001 /*
002 * Copyright 2006 Stephen J. McConnell.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
013 * implied.
014 *
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package net.dpml.lang;
020
021 import java.io.Writer;
022 import java.io.IOException;
023 import java.lang.reflect.Constructor;
024 import java.lang.reflect.InvocationTargetException;
025 import java.lang.reflect.Array;
026 import java.beans.Expression;
027 import java.util.Arrays;
028 import java.util.ArrayList;
029 import java.util.Iterator;
030
031 import net.dpml.util.Logger;
032
033 /**
034 * Plugin part strategy implementation datatype.
035 * @author <a href="http://www.dpml.net">The Digital Product Meta Library</a>
036 * @version 1.0.0
037 */
038 public class Plugin extends Part
039 {
040 private final String m_classname;
041 private final Value[] m_params;
042
043 /**
044 * Creation of an new plugin datatype.
045 * @param logger the assigned logging channel
046 * @param info the part info descriptor
047 * @param classpath the classpath descriptor
048 * @param classname the target class
049 * @exception IOException if an I/O error occurs
050 */
051 public Plugin(
052 Logger logger, Info info, Classpath classpath, String classname )
053 throws IOException
054 {
055 this( logger, info, classpath, classname, new Value[0] );
056 }
057
058 /**
059 * Creation of an new plugin datatype.
060 * @param logger the assigned logging channel
061 * @param info the part info descriptor
062 * @param classpath the classpath descriptor
063 * @param classname the target class
064 * @param params an array of default value arguments
065 * @exception IOException if an I/O error occurs
066 */
067 public Plugin(
068 Logger logger, Info info, Classpath classpath, String classname, Value[] params )
069 throws IOException
070 {
071 super( logger, info, classpath );
072 if( null == classname )
073 {
074 throw new NullPointerException( "classname" );
075 }
076 if( null == params )
077 {
078 throw new NullPointerException( "params" );
079 }
080 m_classname = classname;
081 m_params = params;
082 }
083
084 /**
085 * Return the part content or null if the result type is unresolvable
086 * relative to the supplied class argument.
087 * @param c the content class
088 * @return the content
089 * @exception IOException if an IO error occurs
090 */
091 protected Object getContent( Class c ) throws IOException
092 {
093 if( Class.class.equals( c ) )
094 {
095 return getPluginClass();
096 }
097 else
098 {
099 return super.getContent( c );
100 }
101 }
102
103 /**
104 * Get the target classname.
105 * @return the classname
106 */
107 public String getClassname()
108 {
109 return m_classname;
110 }
111
112 /**
113 * Get the array of default constructor values.
114 * @return the value array
115 */
116 public Value[] getValues()
117 {
118 return m_params;
119 }
120
121 /**
122 * Get the default plugin class.
123 * @return the plugin class
124 */
125 public Class getPluginClass()
126 {
127 ClassLoader classloader = getClassLoader();
128 String classname = getClassname();
129 try
130 {
131 return classloader.loadClass( classname );
132 }
133 catch( ClassNotFoundException e )
134 {
135 final String error =
136 "Plugin class [" + classname + "] not found.";
137 throw new IllegalStateException( error );
138 }
139 }
140
141 /**
142 * Instantiate a value.
143 * @param args supplimentary arguments
144 * @return the resolved instance
145 * @exception Exception if a deployment error occurs
146 */
147 public Object instantiate( Object[] args ) throws Exception
148 {
149 ClassLoader classloader = getClassLoader();
150 ClassLoader context = Thread.currentThread().getContextClassLoader();
151 Thread.currentThread().setContextClassLoader( classloader );
152 try
153 {
154 Value[] values = getValues();
155 Class c = getPluginClass();
156 Object[] params = Construct.getArgs( null, values, args );
157 return instantiate( c, params );
158 }
159 finally
160 {
161 Thread.currentThread().setContextClassLoader( context );
162 }
163 }
164
165 /**
166 * Test if this instance is equal to the supllied object.
167 * @param other the other object
168 * @return true if the supplied object is equal to this instance
169 */
170 public boolean equals( Object other )
171 {
172 if( super.equals( other ) )
173 {
174 if( other instanceof Plugin )
175 {
176 Plugin plugin = (Plugin) other;
177 if( !m_classname.equals( plugin.m_classname ) )
178 {
179 return false;
180 }
181 else
182 {
183 return Arrays.equals( m_params, plugin.m_params );
184 }
185 }
186 else
187 {
188 return false;
189 }
190 }
191 else
192 {
193 return false;
194 }
195 }
196
197 /**
198 * Get the has code for this instance.
199 * @return the hash value
200 */
201 public int hashCode()
202 {
203 int hash = m_classname.hashCode();
204 for( int i=0; i<m_params.length; i++ )
205 {
206 hash ^= m_params[i].hashCode();
207 }
208 return hash;
209 }
210
211
212 /**
213 * Encode the pluginstrategy to XML.
214 * @param writer the output stream writer
215 * @param pad the character offset
216 * @exception IOException if an I/O error occurs
217 */
218 protected void encodeStrategy( Writer writer, String pad ) throws IOException
219 {
220 String classname = getClassname();
221 writer.write( "\n" + pad + "<strategy xsi:type=\"plugin\" class=\"" );
222 writer.write( classname );
223 writer.write( "\"" );
224 if( getValues().length > 0 )
225 {
226 writer.write( ">" );
227 Value[] values = getValues();
228 VALUE_ENCODER.encodeValues( writer, values, pad + " " );
229 writer.write( "\n" + pad + "</strategy>" );
230 }
231 else
232 {
233 writer.write( "/>" );
234 }
235 }
236
237 /**
238 * Create a factory using a supplied class and command line arguments.
239 *
240 * @param clazz the the factory class
241 * @param args the command line args
242 * @return the plugin instance
243 * @exception IOException if a plugin creation error occurs
244 * @exception InvocationTargetException if a plugin constructor invocation error occurs
245 */
246 public static Object instantiate( Class clazz, Object[] args ) throws IOException, InvocationTargetException
247 {
248 if( null == clazz )
249 {
250 throw new NullPointerException( "clazz" );
251 }
252 if( null == args )
253 {
254 throw new NullPointerException( "args" );
255 }
256 for( int i=0; i < args.length; i++ )
257 {
258 Object p = args[i];
259 if( null == p )
260 {
261 final String error =
262 "User supplied instantiation argument at position ["
263 + i
264 + "] for the class ["
265 + clazz.getName()
266 + "] is a null value.";
267 throw new NullPointerException( error );
268 }
269 }
270
271 if( clazz.getConstructors().length == 1 )
272 {
273 Constructor constructor = getSingleConstructor( clazz );
274 return instantiate( constructor, args );
275 }
276 else
277 {
278 try
279 {
280 Expression expression = new Expression( clazz, "new", args );
281 return expression.getValue();
282 }
283 catch( InvocationTargetException e )
284 {
285 throw e;
286 }
287 catch( PartException e )
288 {
289 throw e;
290 }
291 catch( Throwable e )
292 {
293 final String error =
294 "Class instantiation error [" + clazz.getName() + "]";
295 throw new PartException( error, e );
296 }
297 }
298 }
299
300 private static Object instantiate( Constructor constructor, Object[] args )
301 throws PartException, InvocationTargetException
302 {
303 Object[] arguments = populate( constructor, args );
304 return newInstance( constructor, arguments );
305 }
306
307 private static Object[] populate( Constructor constructor, Object[] args ) throws PartException
308 {
309 if( null == constructor )
310 {
311 throw new NullPointerException( "constructor" );
312 }
313 if( null == args )
314 {
315 throw new NullPointerException( "args" );
316 }
317
318 Class[] classes = constructor.getParameterTypes();
319 Object[] arguments = new Object[ classes.length ];
320 ArrayList list = new ArrayList();
321 for( int i=0; i < args.length; i++ )
322 {
323 list.add( args[i] );
324 }
325
326 //
327 // sweep though the construct arguments one by one and
328 // see if we can assign a value based on the supplied args
329 //
330
331 for( int i=0; i < classes.length; i++ )
332 {
333 Class clazz = classes[i];
334 Iterator iterator = list.iterator();
335 while( iterator.hasNext() )
336 {
337 Object object = iterator.next();
338 Class c = object.getClass();
339 if( isAssignableFrom( clazz, c ) )
340 {
341 arguments[i] = object;
342 list.remove( object );
343 break;
344 }
345 }
346 }
347
348 //
349 // if any arguments are unresolved then check if the argument type
350 // is something we can implicity establish
351 //
352
353 for( int i=0; i < arguments.length; i++ )
354 {
355 if( null == arguments[i] )
356 {
357 Class c = classes[i];
358 if( c.isArray() )
359 {
360 arguments[i] = getEmptyArrayInstance( c );
361 }
362 else
363 {
364 final String error =
365 "Unable to resolve a value for a constructor parameter."
366 + "\nConstructor class: " + constructor.getDeclaringClass().getName()
367 + "\nParameter class: " + c.getName()
368 + "\nParameter position: " + ( i + 1 );
369 throw new PartException( error );
370 }
371 }
372 }
373 return arguments;
374 }
375
376 private static boolean isAssignableFrom( Class clazz, Class c )
377 {
378 if( clazz.isPrimitive() )
379 {
380 if( Integer.TYPE == clazz )
381 {
382 return Integer.class.isAssignableFrom( c );
383 }
384 else if( Boolean.TYPE == clazz )
385 {
386 return Boolean.class.isAssignableFrom( c );
387 }
388 else if( Byte.TYPE == clazz )
389 {
390 return Byte.class.isAssignableFrom( c );
391 }
392 else if( Short.TYPE == clazz )
393 {
394 return Short.class.isAssignableFrom( c );
395 }
396 else if( Long.TYPE == clazz )
397 {
398 return Long.class.isAssignableFrom( c );
399 }
400 else if( Float.TYPE == clazz )
401 {
402 return Float.class.isAssignableFrom( c );
403 }
404 else if( Double.TYPE == clazz )
405 {
406 return Double.class.isAssignableFrom( c );
407 }
408 else
409 {
410 final String error =
411 "Primitive type ["
412 + c.getName()
413 + "] not supported.";
414 throw new RuntimeException( error );
415 }
416 }
417 else
418 {
419 return clazz.isAssignableFrom( c );
420 }
421 }
422
423 private static Object newInstance( Constructor constructor, Object[] arguments )
424 throws PartException, InvocationTargetException
425 {
426 try
427 {
428 Object instance = constructor.newInstance( arguments );
429 //getMonitor().pluginInstantiated( instance );
430 return instance;
431 }
432 catch( InvocationTargetException e )
433 {
434 throw e;
435 }
436 catch( Throwable e )
437 {
438 final String error =
439 "Cannot create an instance of ["
440 + constructor.getDeclaringClass().getName()
441 + "] due to an instantiation failure.";
442 throw new PartException( error, e );
443 }
444 }
445
446 private static Constructor getSingleConstructor( Class clazz ) throws PartException
447 {
448 if( null == clazz )
449 {
450 throw new NullPointerException( "clazz" );
451 }
452 Constructor[] constructors = clazz.getConstructors();
453 if( constructors.length < 1 )
454 {
455 final String error =
456 "Target class ["
457 + clazz.getName()
458 + "] does not declare a public constructor.";
459 throw new PartException( error );
460 }
461 else if( constructors.length > 1 )
462 {
463 final String error =
464 "Target class ["
465 + clazz.getName()
466 + "] declares multiple public constructors.";
467 throw new PartException( error );
468 }
469 else
470 {
471 return constructors[0];
472 }
473 }
474
475 /**
476 * Constructs an empty array instance.
477 * @param clazz the array class
478 * @return the empty array instance
479 * @exception RepositoryException if an error occurs
480 */
481 private static Object[] getEmptyArrayInstance( Class clazz ) throws PartException
482 {
483 try
484 {
485 return (Object[]) Array.newInstance( clazz.getComponentType(), 0 );
486 }
487 catch( Throwable e )
488 {
489 final String error =
490 "Internal error while attempting to construct an empty array for the class: "
491 + clazz.getName();
492 throw new PartException( error, e );
493 }
494 }
495 }